#ifndef TR_COMMON_C
#define TR_COMMON_C

#include "tr_definitions.h"

/*
 * new_node(): Create new node, add it to node list, and return it
 */
static NODE *new_node(const CnetAddr addr, VECTOR *node_list) {
  NODE *n = malloc(sizeof(NODE));
  
  n->addr = addr;
  WINDOW* w = malloc(sizeof(WINDOW));
  n->window = w;
  n->window->pkts = vector_new();
  n->window->pkt_ids = vector_new();
  n->window->next_seq = 1;
  n->window->size = TR_WINDOW_SIZE;
  
  
  n->acks = vector_new();
  n->ack_timeout = (CnetTime)TR_ACK_TIMEOUT;
  
  vector_append(*node_list, n, sizeof(NODE));
  return n;
}

/*
 * get_node(): Get node from node list by address and return it
 */
static NODE *get_node(const CnetAddr addr, VECTOR *node_list) {
  NODE *n;
  size_t len;
  for (int i = 0; i < vector_nitems(*node_list); i++) {
    n = vector_peek(*node_list, i, &len);
    if (n->addr == addr)
      return n;
  }
  
  return NULL;
}

/*
 * add_packet(): Add a packet to transport window
 */
static void add_packet(PACKET *pkt, WINDOW *w) {
  vector_append(w->pkts, pkt, sizeof(PACKET));
}

/*
 * get_packet(): Get a packet from window by sequence number and return it
 */
static PACKET *get_packet(const uint16_t seq, WINDOW *w) {
  PACKET *pkt;
  size_t len;

  for (int i = 0; i < vector_nitems(w->pkts); i++) {
    pkt = vector_peek(w->pkts, i, &len);
    if (pkt->seq == seq)
      return pkt;
  }
  
  return NULL;
}

/*
 * remove_packet(): Remove a packet from window by sequence number and return it
 */
static PACKET *remove_packet(const uint16_t seq, WINDOW *w) {
  size_t len;

  for (int i = 0; i < vector_nitems(w->pkts); i++) {
    if (((PACKET *)vector_peek(w->pkts, i, &len))->seq == seq)
      return vector_remove(w->pkts, i, &len);
  }
  
  return NULL;
}

/*
 * add_id(): Add a packet ID to the transport window
 */
static void add_id(PACKET_ID *pkt_id, WINDOW *w) {
  vector_append(w->pkt_ids, pkt_id, sizeof(PACKET_ID));
}

/*
 * get_id(): Get a packet ID from window by sequence number and return it
 */
static PACKET_ID *get_id(const uint16_t seq, WINDOW *w) {
  PACKET_ID *pkt_id;
  size_t len;

  for (int i = 0; i < vector_nitems(w->pkt_ids); i++) {
    pkt_id = vector_peek(w->pkt_ids, i, &len);
    if (pkt_id->seq == seq)
      return pkt_id;
  }
  
  return NULL;
}

/*
 * remove_id(): Remove a packet ID from window by sequence number and return it
 */
static PACKET_ID *remove_id(const uint16_t seq, WINDOW *w) {
  PACKET_ID *pkt_id;
  size_t len;

  for (int i = 0; i < vector_nitems(w->pkt_ids); i++) {
    pkt_id = vector_remove(w->pkt_ids, i, &len);
    if (pkt_id->seq == seq)
      return pkt_id;
  }
  
  return NULL;
}

/*
 * pos_next_packet(): Return position of oldest packet in window
 */
static int pos_next_packet(WINDOW *w) {
  PACKET *pkt = NULL;
  size_t len;
  uint16_t seq = 0;
  int pos = 0;
  
  for (int i = 0; i < vector_nitems(w->pkts); i++) {
    pkt = vector_peek(w->pkts, i, &len);
    if (i == 0)
      seq = pkt->seq;
    else {
      if (pkt->seq < seq) {
	seq = pkt->seq;
	pos = i;
      }
    }
  }
  
  if (seq)
    return pos;
  else
    return -1;
}

/*
 * next_packet(): Get oldest packet in window and return it, or NULL if empty
 */
static PACKET *next_packet(WINDOW *w) {
  size_t len;
  int pos = pos_next_packet(w);
  
  if (pos >= 0)
    return vector_peek(w->pkts, pos, &len);
  else
    return NULL;
}

/*
 * remove_next_packet(): Remove the oldest packet in window and return it, or NULL
 * if empty
 */
static PACKET *remove_next_packet(WINDOW *w) {
  size_t len;
  int pos = pos_next_packet(w);
  
  if (pos >= 0)
    return vector_remove(w->pkts, pos, &len);
  else
    return NULL;
}

/*
 * window_full(): Return true if window has reach current capacity, else false
 */
static bool window_full(WINDOW *w) {
  return (vector_nitems(w->pkts) >= w->size);
}

/*
 * window_free(): Return true if window can receive another packet, else false
 */
static bool window_free(WINDOW *w) {
  return (vector_nitems(w->pkts) < w->size);
}

/*
 * window_ready(): Return true if next sequence number is received, else false
 */
static bool window_ready(WINDOW *w) {
  PACKET *pkt = next_packet(w);
  return ((pkt != NULL) && (pkt->seq == w->next_seq));
}

/*
 * in_window(): Return if packet sequence number is within window
 */
static bool in_window(PACKET *pkt, WINDOW *w) {
  if (w->next_seq + w->size < SEQ_MAX)	// if sequence numbers do not wrap in window
    return ((pkt->seq >= w->next_seq) && (pkt->seq < w->next_seq + w->size));
  else
    return ((pkt->seq == SEQ_MAX) || 
      ((w->next_seq <= pkt->seq) && (pkt->seq < (w->next_seq + w->size) % SEQ_MAX)));
}

/*
 * add_ack(): Add acknowledgment to pending queue
 */
static void add_ack(PACKET *pkt, bool ack, NODE *n) {
  ACK_PENDING ack_pending;
  ack_pending.seq_ack = pkt->seq;
  ack_pending.ack = ack;
  vector_append(n->acks, &ack_pending, sizeof(ACK_PENDING));
}

/*
 * next_ack(): Remove next ACK pending from list and return it
 */
static ACK_PENDING *remove_next_ack(NODE *n) {
  size_t len;

  if (vector_nitems(n->acks) > 0 )
    return vector_remove(n->acks, 0, &len);
  else
    return NULL;
}

static uint16_t head_crc(PACKET *pkt) {
  return CNET_crc16((unsigned char *)pkt, PACKET_HEADER_SIZE - sizeof(pkt->crc_head) - sizeof(pkt->crc_msg));
}

static uint16_t msg_crc(PACKET *pkt) {
  return CNET_crc16((unsigned char *)&(pkt->msg), pkt->len);
}

#endif

